package com.best.oasis.settlement.dao.base;


import java.io.Serializable;
import java.nio.charset.Charset;
import java.sql.Connection;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.support.TransactionSynchronizationAdapter;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.util.Assert;

import com.best.oasis.settlement.common.configmapping.EntitySequenceMapping;
import com.best.oasis.settlement.common.entity.base.BaseBizEntity;
import com.best.oasis.settlement.common.entity.base.BaseEntity;
import com.best.oasis.settlement.dao.sysLog.SysLogDao;
import com.best.oasis.settlement.util.CodeUtil;
import com.best.oasis.settlement.util.SecurityUtil;
import com.best.oasis.settlement.util.exception.DAOException;
import com.best.oasis.settlement.util.exception.OptLockException;
import com.best.oasis.settlement.util.exception.QueryException;
import com.best.oasis.settlement.util.page.BasePageSO;
import com.best.oasis.settlement.util.page.PageList;

/**
 * This class serves as the Base class for all other DAOs - namely to hold
 * common CRUD methods that they might all use. You should only need to extend
 * this class when your require custom CRUD logic.
 * 
 * <p>
 * To register this class in your Spring context file, use the following XML.
 * 
 * <pre>
 *      &lt;bean id=&quot;fooDao&quot; class=&quot;oasis.dao.hibernate.GenericDaoHibernate&quot;&gt;
 *          &lt;constructor-arg value=&quot;oasis.model.Foo&quot;/&gt;
 *          &lt;property name=&quot;sessionFactory&quot; ref=&quot;sessionFactory&quot;/&gt;
 *      &lt;/bean&gt;
 * </pre>
 * 
 * @param <T>
 *            a type variable
 * @param <PK>
 *            the primary key for that type
 */
public class BaseDaoImpl<T extends BaseEntity, PK extends Serializable> implements BaseDao<T, PK> {
	  /**
     * if sqlmapping include chinese character ,have to fix the charset
     */
    static {
        Resources.setCharset(Charset.forName("UTF-8"));
    }
    
	/**
	 * Logger for this class
	 */
	private static final Log logger = LogFactory.getLog(BaseDaoImpl.class);
	
	@Autowired
	protected SysLogDao sysLogDao;
	
	
	
	/**
     * 预定义的Statement
     */
	protected static final String POSTFIX_INSERT = "insert";

	protected static final String POSTFIX_UPDATE = "update";

	protected static final String POSTFIX_GET_BY_ID = "getById";
    
	protected static final String POSTFIX_GET_ALL = "getAll";
    
	protected static final String POSTFIX_GET_LIST_BY_SO = "getListBySo";

	protected static final String POSTFIX_GET_COUNT_BY_SO = "getCountBySo";

	protected static final String POSTFIX_GET_LIST_BY_FIELD = "getListByField";
    
	protected static final String POSTFIX_GET_LIST_BY_IDS = "getListByIds";
    
	protected static final String POSTFIX_DELETE_BY_ID = "deleteById";
    
	protected static final String POSTFIX_DELETE_BY_IDS = "deleteByIds";
   
	protected static final String POSTFIX_SPLIT = ".";
    
    	
    protected Class<T> entityClass = null;
    
	/**
	 * Constructor that takes in a class to see which type of entity to persist
	 * 
	 * @param persistentClass
	 *            the class type you'd like to persist
	 */
	public BaseDaoImpl(final Class<T> persistentClass) {
		this.entityClass = persistentClass;
		//entityClass =  (Class) this.getClass().getInterfaces()[0];
	}
	protected String getStatementPrefix() {
	    return entityClass.getSimpleName() + POSTFIX_SPLIT;
	}
		
	@SuppressWarnings("unchecked")
	public T get(PK id) {
		try {
            T entity = (T) sqlSessionTemplate.selectOne(getStatementPrefix() + POSTFIX_GET_BY_ID, id);

            return entity;
        } catch (Exception e) {
            logger.error(getStatementPrefix() + "error occured when method get invocked.");
            logger.error(e);
            e.printStackTrace();
            throw new DAOException(e.getCause());
        }

	}
	
	@Override
	public boolean exists(PK id) {
		T entity = get(id);
		return entity != null;
	}
	
	@SuppressWarnings("unchecked")
	public T create(T entity) {
		// 父类为BaseBizEntity的entity保存时需要自动记录username、createTime和lastUpdateTime属性
		if (entity instanceof BaseBizEntity) {
			BaseBizEntity baseInfo = (BaseBizEntity) entity;
			String username = SecurityUtil.getLoginUsername();
			baseInfo.setCreator(username);
			baseInfo.setLastUpdator(username);
			String nickName = SecurityUtil.getLoginUserNick();
			baseInfo.setCreatorName(nickName);
			baseInfo.setLastUpdatorName(nickName);
			
			//getCode(class, id)
//			Long id = nextCodeSequence();
//			baseInfo.setId(id);
			if(StringUtils.isEmpty(baseInfo.getCode())){
				String code = CodeUtil.generateCode(this.entityClass,nextCodeSequence());
				baseInfo.setCode(code);
			}
			baseInfo.setCreateTime(new Date());
			baseInfo.setLastUpdateTime(baseInfo.getCreateTime());
		}
		try {
			sqlSessionTemplate.insert(getStatementPrefix() + POSTFIX_INSERT,entity);
			T result = get((PK) entity.getId());
			//sysLogDao.log(this.entityClass.toString(),"create:" + entity.toString());
			return result;
		} catch (Exception e) {
			logger.error(getStatementPrefix() + "error occured when method create invocked.");
			logger.error(e);
			throw new DAOException(e);
		}
    }
	 // 获取下一个ID
    protected Long nextCodeSequence(){
    	Assert.notNull(this.entityClass);
    	String sequenceName = EntitySequenceMapping.MAP.get(this.entityClass);
    	if(StringUtils.isBlank(sequenceName)){
    		sequenceName = EntitySequenceMapping.DEFAULT_CODE_SEQUENCE;
    	}
    	 Map<String,String> map = new HashMap<String, String>();
         map.put("sequenceName",sequenceName );
    	Long res =  (Long)sqlSessionTemplate.selectOne("sqlmap.common.getCodeSequence", map);
    	
    	
    	return res.longValue();
    }

    @SuppressWarnings("unchecked")
	public T update(T object) {
    	T oldObject = null;
    	// 父类为BaseBizEntity的entity保存时需要自动记录username、createTime和lastUpdateTime属性
		if (object instanceof BaseBizEntity) {
			BaseBizEntity baseBizEntity = (BaseBizEntity) object;
			String username = SecurityUtil.getLoginUsername();
			String nickName = SecurityUtil.getLoginUserNick();
			baseBizEntity.setLastUpdatorName(nickName);
			baseBizEntity.setLastUpdator(username);
			baseBizEntity.setLastUpdateTime(new Date());

			oldObject = get((PK)baseBizEntity.getId());
		}
		int records = sqlSessionTemplate.update(getStatementPrefix() + POSTFIX_UPDATE, object);
        if (records == 0 || records == -1) {
            throw new OptLockException("数据已被修改或删除！请刷新后重试！");
        }
       T result = get((PK)object.getId());
		
		//log the change information
		StringBuilder logDesc = new StringBuilder("update:");
		logDesc.append("\r\n");
		if(oldObject != null){
			logDesc.append("old:");
			logDesc.append(oldObject.toString());
			logDesc.append("\r\n");
		}
		
		if( object!= null && !object.equals(oldObject) ){
			logDesc.append("new:");
			logDesc.append(object.toString());
			sysLogDao.log(this.entityClass.toString(), logDesc.toString());
		}
		return result;
    }
	/*
	 * (non-Javadoc)
	 * 
	 * @see com.best.eLogistics.dao.BaseDao#remove(PK)
	 */
	public void remove(PK id) {
        try {
        	T object = get(id);
        	sqlSessionTemplate.delete(getStatementPrefix() + POSTFIX_DELETE_BY_ID, id);
            sysLogDao.log(this.entityClass.toString(), "remove:" + object);
            
        } catch (Exception e) {
            logger.error(getStatementPrefix() + "error occured when method remove invocked.");
            logger.error(e);
            throw new DAOException(e);
        }
		
	}
	public void remove(List<PK> ids) {
		 try {
	            for (PK id : ids) {
	            	T object = get(id);
	            	this.getBatchSqlSessionTemplate().delete(getStatementPrefix() + POSTFIX_DELETE_BY_ID, id);
	                sysLogDao.log(this.entityClass.toString(), "remove:" + object);
	            }
	            this.getBatchSqlSessionTemplate().flushStatements();
	        } catch (Exception e) {
	            logger.error(getStatementPrefix() + "error occured when method remove ids invocked.");
	            logger.error(e);
	            throw new DAOException(e);
	        }
    }
	

	@Override
	public List<Long> batchCreate(List<T> entityList) {
		 List<Long> rltList = new ArrayList<Long>();
	        try {
	          	String username = SecurityUtil.getLoginUsername();
	        	String nickName = SecurityUtil.getLoginUserNick();
	        	Date now = new Date();
	            for (T entity : entityList) {
	        		if (entity instanceof BaseBizEntity) {
	        			BaseBizEntity baseInfo = (BaseBizEntity) entity;
	        			
	        			baseInfo.setCreator(username);
	        			baseInfo.setLastUpdator(username);
	        			
	        			baseInfo.setCreatorName(nickName);
	        			baseInfo.setLastUpdatorName(nickName);
	        			
	        			if(StringUtils.isEmpty(baseInfo.getCode())){
	        				String code = CodeUtil.generateCode(this.entityClass,nextCodeSequence());
	        				baseInfo.setCode(code);
	        			}
	        			baseInfo.setCreateTime(now);
	        			baseInfo.setLastUpdateTime(now);
	        		}
	            	this.getBatchSqlSessionTemplate().insert(getStatementPrefix() + POSTFIX_INSERT, entity);
	            }
	            this.getBatchSqlSessionTemplate().flushStatements();
	            for (T entity : entityList) {
	                rltList.add(entity.getId());
	            }
	        } catch (Exception e) {
	            logger.error(getStatementPrefix() + "error.createPOBatch");
	            logger.error(e);
	            throw new DAOException(e);
	        }
	        return rltList;
	}

	@Override
	public void batchUpdate(List<T> entityList) {
		 try {
	            String username = SecurityUtil.getLoginUsername();
				String nickName = SecurityUtil.getLoginUserNick();
				Date now = new Date();
	            for (T entity : entityList) {
	            	if (entity instanceof BaseBizEntity) {
	        			BaseBizEntity baseBizEntity = (BaseBizEntity) entity;
	        			
	        			baseBizEntity.setLastUpdatorName(nickName);
	        			baseBizEntity.setLastUpdator(username);
	        			baseBizEntity.setLastUpdateTime(now);
	        		}
	            	this.getBatchSqlSessionTemplate().update(getStatementPrefix() + POSTFIX_UPDATE, entity);
	            }
	            this.getBatchSqlSessionTemplate().flushStatements();
	        } catch (Exception e) {
	            logger.error(getStatementPrefix() + "error.updatePOBatch");
	            logger.error(e);
	            throw new DAOException(e);
	        }
	}
	

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.best.eLogistics.dao.BaseDao#getAll()
	 */
	@SuppressWarnings("unchecked")
	public List<T> getAll() {
	    Long startTime = System.currentTimeMillis();
        try {
            List<T> retList = (List<T>) sqlSessionTemplate.selectList(getStatementPrefix()+POSTFIX_GET_ALL);
            return retList;
        } catch (Exception e) {
            throw new QueryException(e);
        } finally {
            logger.debug("Record loading took " + (System.currentTimeMillis() - startTime));
        }
	}

	@SuppressWarnings("unchecked")
	public List<T> getAll(BasePageSO so) {
	 Long startTime = System.currentTimeMillis();
        try {
            List<T> retList = (List<T>) sqlSessionTemplate.selectList(getStatementPrefix() + POSTFIX_GET_LIST_BY_SO ,so);
            return retList;
        } catch (Exception e) {
            throw new QueryException(e);
        } finally {
            logger.debug("Record loading took " + (System.currentTimeMillis() - startTime));
        }
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.best.eLogistics.dao.BaseDao#getPageList(com.best.eLogistics.webapp
	 * .util.page.BasePageSO)
	 */
	@SuppressWarnings("unchecked")
	public PageList<T> getPageList(BasePageSO so) {
		PageList<T> result = new PageList<T>(so);
		List<T> list;
        Long startTime = System.currentTimeMillis();
        try {
            RowBounds rowBounds = new RowBounds((so.getPageNumber() - 1) * so.getObjectsPerPage(), so.getObjectsPerPage());
           
            list = (List<T>) sqlSessionTemplate.selectList(getStatementPrefix() + POSTFIX_GET_LIST_BY_SO ,so,rowBounds);
            int count = getPaginatedListCount(so);
            
            result.setFullListSize(count);
            result.setList(list);
            
            return result;
        } catch (Exception e) {
            throw new QueryException(e);
        } finally {
            logger.debug("One page record loading took " + (System.currentTimeMillis() - startTime));
        }
	}
    protected int getPaginatedListCount(BasePageSO so) {
        Long startTime = System.currentTimeMillis();
        try {
        	Long count = (Long) sqlSessionTemplate.selectOne(getStatementPrefix() + POSTFIX_GET_COUNT_BY_SO,so);
            return count.intValue();
        } catch (Exception e) {
            throw new QueryException(e);
        } finally {
            logger.debug("Total record count loading took " + (System.currentTimeMillis() - startTime));
        }

    }

	

	@SuppressWarnings("unchecked")
	@Override
	public List<T> getByIds(List<PK> ids) {
		 Long startTime = System.currentTimeMillis();
	        try {
	            List<T> retList = (List<T>) sqlSessionTemplate.selectList(getStatementPrefix() + POSTFIX_GET_LIST_BY_IDS ,ids);
	            return retList;
	        } catch (Exception e) {
	            throw new QueryException(e);
	        } finally {
	            logger.debug("Record loading took " + (System.currentTimeMillis() - startTime));
	        }
	}

	@Override
	public T getBy(String fieldName, Object value) {
		Long startTime = System.currentTimeMillis();
        try {
             List<T> retList = getListBy( fieldName,  value) ;
             T result = null ;
             if( retList != null && retList.size() != 0 ){
            	result = retList.get(0);
             }
             return result;
        } catch (Exception e) {
            throw new QueryException(e);
        } finally {
            logger.debug("Record loading took " + (System.currentTimeMillis() - startTime));
        }
        
	}

	@SuppressWarnings("unchecked")
	@Override
	public List<T> getListBy(String fieldName, Object value) {
		Long startTime = System.currentTimeMillis();
        try {
        	 Map<String,Object> map = new HashMap<String, Object>();
             map.put("fieldName", fieldName);
             map.put("colValue", value);
            List<T> retList = (List<T>) sqlSessionTemplate.selectList(getStatementPrefix() + POSTFIX_GET_LIST_BY_FIELD ,map);
            return retList;
        } catch (Exception e) {
            return null;
            //throw new QueryException(e);
        } finally {
            logger.debug("Record loading took " + (System.currentTimeMillis() - startTime));
        }
	}
	
///////////////////////////////////////////////////////////////////////////
	private ThreadLocal<SqlSession> tl = new ThreadLocal<SqlSession>();

	@Autowired
	protected SqlSessionTemplate sqlSessionTemplate;
	
	protected SqlSession getSqlSessionTemplate() {
		return sqlSessionTemplate;
	}

	protected SqlSession getSqlSession() {
		Connection conn = sqlSessionTemplate.getConnection();
		SqlSessionFactory sqlSessionFactory = sqlSessionTemplate
				.getSqlSessionFactory();
		final SqlSession sqlSession = sqlSessionFactory.openSession(
				ExecutorType.BATCH, conn);
		return sqlSession;
	}

	/**
	 * SqlSessionDaoSupport中有对SqlSessionTemplate的自动注入,
	 * 故只能有一个SqlSessionTemplate在XML中配置
	 */
	protected SqlSession getBatchSqlSessionTemplate() {
		if (tl.get() == null) {
			tl.set(this.getSqlSession());
			/**
			 * 注册至Spring事务,由TransactionSynchronizationUtils#invokeAfterCompletion
			 * 回调TransactionSynchronization#afterCompletion方法,去关闭SqlSession,并
			 * 从ThreadLocal中清除,用法类似@see SqlSessionUtils#getSqlSession 155行
			 */
			TransactionSynchronizationManager
					.registerSynchronization(new SqlSessionSynchronization());
		}
		return tl.get();
	}

	private final class SqlSessionSynchronization extends
			TransactionSynchronizationAdapter {
		@Override
		public void afterCompletion(int status) {
			if (tl.get() != null) {
				logger.info("Transaction synchronization closing SqlSession ["
						+ tl.get() + "],with ExecutorType.BATCH");
				try {
					tl.get().close();
				} finally {
					tl.set(null);
				}
			}
		}
	}
}
